#ifndef __CLI_PARSE__BSP_H__
#define __CLI_PARSE__BSP_H__

#include <stack>
#include <vector>
//#include "../../Epsilon/Epsilon3D/vector3.h"
#include "../../Epsilon/Epsilon3D/plane.h"

#ifndef null
#define null 0
#endif


/**
 * Base class for a vertex in 3d space...
 *
 *   Any vertex that contains more than a simple position
 *     should inherit this base class either directly or
 *       indirectly.
 *
**/
class eTB_Vertex {
public:
  eTB_Vertex (void) {
    pos_.x = 0.0f;
    pos_.y = 0.0f;
    pos_.z = 0.0f;
  }

  eTB_Vertex (e3dVector3 pos) {
    pos_ = pos;
  }

  operator const e3dVector3 (void) {
    return pos_;
  }

  void
    setPos (const e3dVector3& pos) {
      pos_ = pos;
    }

  void
    setPos (float x, float y, float z) {
      pos_ = e3dVector3 (x, y, z);
    }

  const e3dVector3&
    getPos (void) {
      return pos_;
    }

  virtual
    eTB_Vertex*
      duplicate (void) {
        return new eTB_Vertex (pos_);
      }

protected:
  e3dVector3 pos_;


  /** This may or may not be useful in the future... */
  static const int size = sizeof (e3dVector3);
};

class eTB_Vertex_UL : public eTB_Vertex {
public:
  void
    setTexCoords (vec_t u, vec_t v) {
      tex_coords_ [0] = u;
      tex_coords_ [1] = v;
    }

  void
    setColor (float r, float g, float b) {
      color_ = e3dVector3 (r, g, b);
    }

  void
    setNormal (float x, float y, float z) {
      normal_ = e3dVector3 (x, y, z);
  }

  void
    setTangent (float x, float y, float z) {
      tangent_ = e3dVector3 (x, y, z);
  }

  const vec2_t&
    getTexCoords (void) {
      return tex_coords_;
    }

  e3dVector3
    getColor (void) {
      return color_;
    }

  e3dVector3
    getNormal (void) {
      return normal_;
  }

  e3dVector3
    getTangent (void) {
      return tangent_;
  }

  // Cross product of the normal vector and tangent vector...
  //   the binormal is never actually stored, to save space.
  e3dVector3
    getBinormal (void) {
      return normal_ % tangent_;
  }

  virtual
    eTB_Vertex*
      duplicate (void) {
        eTB_Vertex_UL* vtx = new eTB_Vertex_UL ();

        vtx->setPos       (this->getPos ());
        vtx->setTexCoords (tex_coords_ [0], tex_coords_ [1]);
        vtx->setColor     (color_.x,  color_.y,  color_.z);
        vtx->setNormal    (normal_.x, normal_.y, normal_.z);
        vtx->setTangent   (tangent_.x, tangent_.y, tangent_.z);

        return vtx;
      }


protected:
  vec2_t     tex_coords_;
  e3dVector3 color_;
  e3dVector3 normal_;
  e3dVector3 tangent_;
};

class eTB_Polygon {
public:
  eTB_Polygon () {
  }

  virtual ~eTB_Polygon (void) {
    while (! vertices_.empty ()) {
      eTB_Vertex* vtx = vertices_.back ();

      if (vtx != NULL)
        delete vtx;

      vertices_.pop_back ();
    }
  }

  void
  addVertex (eTB_Vertex& vtx) {
    vertices_.push_back (vtx.duplicate ());
  }

//  void
//  addVertex (eTB_Vertex* vtx) {
    //owns_vertices_ = false;
//    vertices_.push_back (vtx);
//  }

  void
  calculatePlane (void) {
    /* Need at least 3 vertices... */
    if (vertices_.size () < 3)
      return;

    /* Assume that ALL vertices are co-planar... */
    plane_.FromPoints (*vertices_ [0], *vertices_ [1], *vertices_ [2]);
  }

  e3dPlane
  getPlane (void) {
    return plane_;
  }

  PLANE_SIDE
  calcPlaneSide (e3dPlane plane) {
    PLANE_SIDE side = SIDE_CROSS;

    std::vector <eTB_Vertex *>::iterator start = vertices_.begin ();
    std::vector <eTB_Vertex *>::iterator end   = vertices_.end   ();

    std::vector <eTB_Vertex *>::iterator vert  = start;

    while (vert != end) {
      PLANE_SIDE new_side = plane.ClassifyPoint (**vert);

      /* If the new vertex is not on the same side of the plane as the last vertex,
           then this polygon crosses the plane. */
      if (vert != start) {
        if (new_side != side)
          return SIDE_CROSS; /* Polygon Crosses Plane. */
      }

      side = new_side;

      ++vert;
    }

    return side;
  }

  eTB_Vertex*
  getVertex (int idx) {
    /* Assert (idx < vertices_.size ()) */
    return vertices_ [idx];
  }

  int
  numVertices (void) {
    return vertices_.size ();
  }

private:
  e3dPlane                   plane_;
  std::vector <eTB_Vertex *> vertices_;
//  bool                       owns_vertices_; /* Future: Allow polygons to use shared vertices... */
};

class eTB_BSP_Tree {
public:
  e3dPlane                   partition;
  std::stack <eTB_Polygon *> polygons;
  eTB_BSP_Tree*              front;
  eTB_BSP_Tree*              back;
};

class eTB_BSP_Compiler {
public:
  void
  buildTree (eTB_BSP_Tree* tree, std::stack <eTB_Polygon *>& polygons) {
    if (polygons.empty ())
      return;

    eTB_Polygon* root = polygons.top ();  polygons.pop ();

    tree->partition = root->getPlane ();
    tree->polygons.push (root);

    std::stack <eTB_Polygon *> front_list;
    std::stack <eTB_Polygon *> back_list;

    eTB_Polygon* poly = polygons.top ();
    polygons.pop ();

    /* .top () == peek () ... */
    while (poly != null) {
      int result = poly->calcPlaneSide (tree->partition);

      switch (result) {
        case SIDE_ON:
          tree->polygons.push (poly);
          break;
        case SIDE_BACK:
          back_list.push (poly);
          break;
        case SIDE_FRONT:
          front_list.push (poly);
          break;
        case SIDE_CROSS:
          eTB_Polygon* front;
          eTB_Polygon* back;

//          splitPolygon    (poly, tree->partition, front, back);
          back_list.push  (back);
          front_list.push (front);

          break;
      }

      poly = polygons.top ();
      polygons.pop        ();
    }

    if (! front_list.empty ()) {
      tree->front = new eTB_BSP_Tree ();

      buildTree (tree->front, front_list);
    }

    if (! back_list.empty ()) {
      tree->back = new eTB_BSP_Tree ();

      buildTree (tree->back, back_list);
    }
  }
};



static
std::stack <eTB_Polygon>
createPolygonList (void) {
  /* For Each Sector ... */

    // NOTE: Record a reference to the parent sector,
    //         the BSP Tree Compiler may split these polygons,
    //           and we need to know where they originally came
    //             from.

    /* Create Polygon for Floor.   */
    /* Create Polygon for Ceiling. */

     /* Push Floor and Ceiling onto list. */

    /* For Each Facing Linedef, Create Polygon(s) for Wall. */

      /* Push Floor Wall   */
      /* Push Ceiling Wall */

  /* Return Polygon List. */
}

/* Sector Floors / Ceilings = (1 or more) Polygons, Wall = (1 or 2) polygons */

/* If a linedef faces a lit sector, apply lighting to the line... */
/* Faces Sector X + Top > Sector X.bottom -> Ceiling Wall */


/** Encapsulate the e3d Immediate Mode Emulator (e3dBegin (...) / e3dEnd (...))
      to build eTB_Polygons. (See: Sector.cpp) **/

//class eTB_PolygonBuilder {
//  eTB_PolygonBuilder (
//};


#endif /* __CLI_PARSE__BSP_H__ */
